package base

import (
	"context"
	"flag"
	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/core/proc"
	"github.com/zeromicro/go-zero/core/service"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"payme3000/framework/config_center"
	"payme3000/framework/logxx"
	"syscall"
	"time"
)

var (
	isDaemon = flag.Bool("d", false, "run service as a daemon with -d=true")
)

type (
	// Cmd 程序入口
	Cmd struct {
		// 默认依赖项
		appConf      *AppConf                    // 程序启动配置, 必须依赖
		configCenter *config_center.ConfigCenter // 配置中心, 可设置不依赖
	}

	// ISetAppConf 实现该接口的模块被视为需要注入AppConf启动配置
	ISetAppConf interface {
		SetAppConf(*AppConf)
	}

	// ISetConfigCenter 实现该接口的模块被视为需要注入ConfigCenter实例
	ISetConfigCenter interface {
		SetConfigCenter(*config_center.ConfigCenter)
	}
)

func NewCmd(options ...OptionFunc) *Cmd {
	c := &Cmd{
		appConf: &AppConf{
			ServiceConf: &service.ServiceConf{},
			ConfigCenterServiceConf: &ConfigCenterServiceConf{
				Log: &logx.LogConf{},
				RestConf: &ConfigCenterRestConf{
					Host: "0.0.0.0",
					Port: 13000,
				},
			},
			DefaultRequestTimeout: 6500,
		},
	}

	for _, fn := range options {
		fn(c.appConf)
	}

	return c
}

// Execute 程序默认的启动函数, 程序的执行从这里开始, 也在这里结束
func (c *Cmd) Execute(
	startup func() (IApplication, error), // 启动回调, 执行后返回自定义的Application实例, 所有服务逻辑都由Application提供实现和维护
) {
	// 程序开始 //

	// 设置go-zero默认发送SIGTERM信号的等待时间, 一般需要比默认的请求超时时间要长一些
	proc.SetTimeToForceQuit(time.Duration(c.appConf.DefaultRequestTimeout+2000) * time.Millisecond)

	// Cmd的执行分为以下阶段
	// 阶段1 定义程序退出时的清理工作
	// 阶段2 解析启动参数
	// 阶段3 检查配置参数
	// 阶段4 构建依赖
	// 阶段5 处理日志配置
	// 阶段6 执行startup函数
	// 阶段7 启动app, 阶段7失败会跳过阶段8
	// 阶段8 等待程序退出信号

	// 阶段1
	// 确保日志刷写到磁盘
	defer func() {
		_ = logx.Close()
	}()
	// 确保发生panic退出时能打印日志
	defer logxx.LogCrash()

	isStartupOk := false // 标记是否启动成功
	func() {
		// 阶段2
		parseFlag()

		// 阶段3
		// 必须指定服务名称
		if c.appConf.Name == "" {
			logx.Error("app name not found")
			return
		}

		// 阶段4
		// 阶段5

		// 阶段6
		// 执行服务提供的startup启动回调, 获取app实例
		if app, err := startup(); err != nil {
			logx.Errorf("startup error with err:%s", err)
			return
		} else {
			// 阶段7
			// 标记成功获取
			isStartupOk = true

			// 退出时确保会stop application
			defer app.Stop()

			// 全局context
			// 所有组件都可以继承该context, 此ctx不用于打印日志
			ctx := context.Background()

			// init application
			app.Init(ctx)
			logx.Info("init app")

			// inject AppConf
			app.SetAppConf(c.appConf)
			logx.Infof("inject AppConf:%+v to app", c.appConf)

			// inject ConfigCenter
			app.SetConfigCenter(c.configCenter)
			logx.Info("inject ConfigCenter to app")

			// start application
			if err := app.Start(); err != nil {
				logx.Error("app start error")
			}

			// 阶段9
			// Start成功与否都在此处等待结束信号
			// 失败也要等待是因为如果部署在k8s时直接退出会被重新拉起
			// main thread is blocking here
			waitSignal()
		}
	}()

	// 阶段8
	if !isStartupOk {
		// startup失败在此处等待结束信号
		// main thread is blocking here
		waitSignal()
	}

	// 程序结束 //
}

func parseFlag() {
	if flag.Parsed() {
		log.Println("flag parsed")
	} else {
		flag.Parse()
	}

	// 是否后台运行
	if *isDaemon {
		args := os.Args[1:]
		i := 0
		argsStr := " "
		for ; i < len(args); i++ {
			if args[i] == "-d=true" {
				args[i] = "-d=false"
			}
			argsStr += args[i] + " "
		}

		var cmd *exec.Cmd
		cmd = exec.Command(os.Args[0], args...)
		_ = cmd.Start()
		log.Println("[PID]", cmd.Process.Pid)

		os.Exit(0)
	}
}

func waitSignal() {
	sigterm := make(chan os.Signal, 1)
	signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
	s := <-sigterm // Await a sigterm signal before safely closing the consumer
	// 收到信号后, 此处尽量不要调用signal.Stop(), 会导致无法再次接收到go-zero二次抛出的SIGTERM信号, 从而过早中断app.Stop()
	logx.Infof("got signal %d, shutting down app", s)
}
